package infoexract.exractreason.samesent.treepattern;

import java.io.IOException;
import java.io.InputStream;
import java.util.Vector;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import nlptools.i2b2.MedNameExtractor;

import org.apache.tools.ant.filters.StringInputStream;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

import datastruct.document.BiomedDocument;
import datastruct.entity.Attribute;
import datastruct.entity.Entity;
import infoexract.InfoExtractor;
import infoexract.exractreason.samesent.treepattern.pattern.SynTaxTreePatternSet;

public class ExtractReasonOneSentTreePattern implements InfoExtractor {

	private SynTaxTreePatternSet patternSet = new SynTaxTreePatternSet();
	public ExtractReasonOneSentTreePattern()
	{
		this.patternSet.ReadFromPatternFile("patterns/syntaxtree/reason_syntree.txt");
	}
	@Override
	public Entity[] ExtractInfo(BiomedDocument document) {
		// TODO Auto-generated method stub
		int size = document.getSize();
		Vector<Entity> entitiesVector = new Vector<Entity>();
		for(int i=0;i<size;i++)
		{
			//In this class, we only deal with the situation that m and r are in the same sentence.
			//So we extract from one single sentence each time.
			Vector<String> medNames = this.getMedicationNames(document.getSenetence(i));
			if(medNames.size()>0/*&&medNames.size()<3*/)
			{
				Vector<Entity> entitiesOneSent = this.getEntitiesFromSyntaxTree(document.getSyntaxTree(i), medNames,document.getSenetence(i));
				entitiesVector.addAll(entitiesOneSent);
			}
		}
		
		size = entitiesVector.size();
		Entity[] entities = new Entity[size];
		for(int i=0;i<size;i++)
		{
			entities[i] = entitiesVector.elementAt(i);
		}
		return entities;
	}
	
	/*************************************/
	//This method is to get all the medication names from one given sentence. A dictionary might be used.
	private Vector<String> getMedicationNames(String sentence)
	{
		return MedNameExtractor.getMedicationNames(sentence);
	}
	
	public static String patternNums="";
	
	
	/************************************/
	//This method is to get the entity and its attributes from a syntax tree. A set of medcation names is given to decide which node in the tree is a entity.
	private Vector<Entity> getEntitiesFromSyntaxTree(String syntaxtreeStr, Vector<String> medNames,String sentence)
	{
		
		Vector<Entity> entities = new Vector<Entity>();
		try {
			DocumentBuilderFactory domfac=DocumentBuilderFactory.newInstance();
			DocumentBuilder dombuilder=domfac.newDocumentBuilder();
			InputStream is=new StringInputStream(syntaxtreeStr);
			Document doc=dombuilder.parse(is);
			
			//Get all the token nodes.
			Vector<Node> tokenNodeVector = new Vector<Node>();
			for(Node childNode = doc.getFirstChild();childNode!=null;childNode = childNode.getNextSibling())
			{
				this.addTokenNodesToVector(childNode, tokenNodeVector);
			}
			
			//Get all the parents for each token node.
			int size = tokenNodeVector.size();
			Node[] tokenNodes = new Node[size];
			Node[][] nodeParentMatrix = new Node[size][];
			for(int i=0;i<size;i++)
			{
				tokenNodes[i] = tokenNodeVector.elementAt(i);
				nodeParentMatrix[i] = this.getParentNodesForOneNode(tokenNodes[i]);
			}
			
			/**************************************/
			//In order to deal with the medicine names that contain more than one token, we fill a array of Entity objects. 
			//Each element in this array represents one token in the sentence. It stores which Entity object should be used when we need to store the information for one medicine name token.
			//What we should do here is to make sure that: 
			//1)Only the Tokens belong to medicine names has the value or it will be null. 
			//2)The tokens belong to one same medicine name share the same Entity object.
			//3)The Entity name should be the full name(containing all the tokens it should have).
			Entity[] entityArray = new Entity[size];
			//First, init the array with null.
			for(int i=0;i<size;i++)
			{
				entityArray[i] = null;
			}
			for(int i=0;i<size;i++)
			{
				Node medNode = tokenNodes[i];
				String medName = medNode.getFirstChild().getNodeValue();
				medName = medName.toLowerCase();
				if(medNames.contains(medName))
				{
					if(i==0||entityArray[i-1]==null) //When we meet a new medicine name.
						entityArray[i] = new Entity(medName);
					else
					{//There is a medicine name token before, so this element should be a part of the same medicine name.
						//Share the same Entity object as the element before.
						entityArray[i] = entityArray[i-1];
						//Add the token to the name of the Entity
						String oldMedName = entityArray[i].getName();
						entityArray[i].setName(oldMedName+" "+medName);
					}
				}
			}
			/******************************************/
			
			//For each token node that is a medication name, find out which other token nodes should be its reason.
			//This is done by filling a array of boolean, for each element of which represent a token node is or not a reason token.
			for(int i=0;i<size;i++)
			{
				Node medNode = tokenNodes[i];												
				if(entityArray[i]!=null)
				{//When it is a medicine
					
					patternNums="";
					
					//Fill a boolean table to show which token is the reason token.
					boolean[] isReasonTable = new boolean[size];
					for(int j=0;j<size;j++)
					{
						if(i==j) 
						{
							isReasonTable[j]=false;
							continue;
						}
						Node reasonNode = tokenNodes[j];
						isReasonTable[j] = this.isReasonToken(medNode, nodeParentMatrix[i], reasonNode, nodeParentMatrix[j],i,j);
					}
					
					//Get the Entity object to fill information in.
					
					Entity entity = entityArray[i];
					//Store the information for the Entity according to the boolean table.
					String reasonStr = "";
					for(int j=0;j<size;j++)
					{
						
						if(isReasonTable[j]) 
						{
							reasonStr += tokenNodes[j].getFirstChild().getNodeValue()+" ";
						}else if(!reasonStr.equals(""))
						{
							reasonStr = reasonStr.trim();
							String attributeCategory = this.categoryOfPhrase(reasonStr);
							if(attributeCategory!=null)
							{
								Attribute attribute = new Attribute(attributeCategory,reasonStr);
								attribute.setSentence(sentence+" Pattens:"+patternNums);
								if(!entity.containsAttribute(attribute)) entity.addAttribute(attribute);
							}
							
							reasonStr = "";
						}
					}
					if(!reasonStr.equals(""))
					{
						reasonStr = reasonStr.trim();
						String attributeCategory = this.categoryOfPhrase(reasonStr);
						if(attributeCategory!=null)
						{
							Attribute attribute = new Attribute(attributeCategory,reasonStr);
							attribute.setSentence(sentence+" Pattens:"+patternNums);
							if(!entity.containsAttribute(attribute)) entity.addAttribute(attribute);
						}
						
						reasonStr = "";
					}
					if(entity.getAttributeNumber()>0&&!entities.contains(entity)) entities.add(entity);
					
				}
			}
			
			
			
		} catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return entities;
	}
	
	//Get all the nodes that is a token in the tree.
	private void addTokenNodesToVector(Node root, Vector<Node> tokenNodeVector)
	{
		if(root.getNodeType()!=Node.ELEMENT_NODE) return;
		if(root.getNodeName().equals("tok")) 
		{
			tokenNodeVector.add(root);
			return;
		}
		for(Node childNode = root.getFirstChild();childNode!=null;childNode = childNode.getNextSibling())
		{
			this.addTokenNodesToVector(childNode, tokenNodeVector);
		}
		
	}
	//Get a array of parents nodes for a node.
	private Node[] getParentNodesForOneNode(Node node)
	{
		Vector<Node> nodeVector = new Vector<Node>();
		Node currentNode = node.getParentNode();
		while(currentNode!=null)
		{
			nodeVector.add(currentNode);
			currentNode = currentNode.getParentNode();
		}
		int size = nodeVector.size();
		Node[] nodes = new Node[size];
		for(int i=0;i<size;i++)
		{
			nodes[i] = nodeVector.elementAt(i);
		}
		return nodes;
	}
	
	//To judge if a token is a reason token for a given medicine. All the parents of the two token has been given as a sequence of node.
	private boolean isReasonToken(Node entityNode,Node[] entityNodeParents,Node attributeNode,Node[] attributeNodeParents, int entityTokenIndex, int attributeTokenIndex)
	{
		String medName = entityNode.getFirstChild().getNodeValue();
		String reasonToken = attributeNode.getFirstChild().getNodeValue();
		
		
		
		//Get the same parent node.
		int indexSameEntityNode = -1;
		int indexSameAttributeNode = -1;
		Node sameNode = null;
		for(int i=0;i<entityNodeParents.length;i++)
		{
			boolean ifGetSameNode = false;
			for(int j=0;j<attributeNodeParents.length;j++)
			{
				if(entityNodeParents[i].getNodeName().equals("#document")) break;
				NamedNodeMap attributeMap = entityNodeParents[i].getAttributes();
				Node idAttribute = attributeMap.getNamedItem("id");
				String entityParentID = idAttribute.getNodeValue();
				
				if(attributeNodeParents[j].getNodeName().equals("#document")) break;
				attributeMap = attributeNodeParents[j].getAttributes();
				idAttribute = attributeMap.getNamedItem("id");
				String attributeParentID = idAttribute.getNodeValue();
				if(entityParentID.equals(attributeParentID))
				{
					indexSameEntityNode = i;
					indexSameAttributeNode = j;
					sameNode = entityNodeParents[i];
					ifGetSameNode = true;
					break;
				}
			}
			if(ifGetSameNode) break;
		}
		if(sameNode==null) return false;
		if(sameNode.getNodeName().equals("#document")) return false;
		if(sameNode.getNodeName().equals("sentence")) return false;
		//if(indexSameEntityNode>10||indexSameAttributeNode>10) return false;
		
		//If there is "sentence" node before the same node, this should not be dealt here.
		for(int i=0;i<=indexSameEntityNode;i++)
		{
			Node parentNode = entityNodeParents[i];
			if(parentNode.getNodeName().equals("sentence")) return false;
		}
		for(int i=0;i<=indexSameAttributeNode;i++)
		{
			Node parentNode = attributeNodeParents[i];
			if(parentNode.getNodeName().equals("sentence")) return false;
		}
		
		boolean ismatch = this.patternSet.isMatch(entityNodeParents, indexSameEntityNode, attributeNodeParents, indexSameAttributeNode);
		if(ismatch) patternNums += ":"+reasonToken+" ";
		return ismatch;
		
	}
	
	//To judge a found word is a reason or someting else.
	private String categoryOfPhrase(String phrase)
	{
		phrase = phrase.toLowerCase();
		if(phrase.contains("day")
			||phrase.contains("week")
			||phrase.contains("month")
			||phrase.contains("year")
			||phrase.contains("dose")
			||phrase.contains("hour"))
			return "Duration";
		if(phrase.equals("for")
				||phrase.equals("with")
				||phrase.equals("as")
				||phrase.equals("to")
				||phrase.equals("by")
				||phrase.equals("the patient")
				||phrase.equals("patient")
				||phrase.equals("he")
				||phrase.equals("his")
				||phrase.equals("him")
				||phrase.equals("she")
				||phrase.equals("her")
				||phrase.equals("discharge")
				||phrase.contains("gold inr"))
			return null;
			
		return "Reason";
	}

}
